import numpy as np
from scipy.spatial.distance import cdist
import warnings
warnings.filterwarnings("ignore")
from sklearn.cluster import k_means
from sklearn.preprocessing import MinMaxScaler
import pandas as pd


def check():
    return True

# 全局变量，用于存储 point_to_gb 的索引
GBi_dict = {}

def GBi_number(idx1, point_to_gb, attr_name=None):
    """
    存储 point_to_gb 的索引，并根据 idx1 区分不同属性。
    默认属性名为 a{idx1+1}，也可以通过 attr_name 传入字符串。
    """
    global GBi_dict
    if attr_name is None:
        attr_name = f"a{idx1 + 1}"
    
    unique_labels = np.unique(point_to_gb)
    for i, label in enumerate(unique_labels):
        indices = np.where(point_to_gb == label)[0]
        GBi_dict[f"GB[{attr_name},{i + 1}]"] = set([f"x{idx + 1}" for idx in indices])
    print(f"属性 {attr_name} 的 point_to_gb 已存储: {point_to_gb}")

def calculate_center_and_radius(gb):
    """
    计算给定数据（球体）集的几何中心以及覆盖数据点的最大距离（半径）。
    """
    data_no_label = gb[:, :]
    center = data_no_label.mean(axis=0)
    radius = np.max(np.linalg.norm(data_no_label - center, axis=1))
    return center, radius

def splits(gb_list, num, k=2):
    """
    若一个数据集内的数据点数量大于阈值，则利用 K-means 将该数据集分裂，
    否则保留原数据集。返回划分后的数据集列表。
    """
    gb_list_new = []
    for gb in gb_list:
        if gb.shape[0] < num:
            gb_list_new.append(gb)
        else:
            gb_list_new.extend(splits_ball(gb, k))
    return gb_list_new

def splits_ball(gb, k):
    """
    对单个数据集 gb 使用 K-means 进行分裂，划分为 k 个小球。
    如果数据点的唯一值少于 k，则调整 k 的值。
    注意：新版的 k_means 返回 (centers, labels, inertia) 三个值。
    """
    ball_list = []
    unique_data = np.unique(gb, axis=0)
    if unique_data.shape[0] < k:
        k = unique_data.shape[0]
    centers, labels, inertia = k_means(X=gb, n_clusters=k, n_init=1, random_state=8)
    for single_label in range(k):
        ball_list.append(gb[labels == single_label, :])
    return ball_list

def assign_points_to_closest_gb(data, gb_centers):
    """
    将每个数据点分配到离其最近的 gb_center，
    返回每个数据点的分配组的索引数组。
    """
    assigned_gb_indices = np.zeros(data.shape[0])
    for idx, sample in enumerate(data):
        t_idx = np.argmin(np.linalg.norm(sample - gb_centers, axis=1))
        assigned_gb_indices[idx] = t_idx
    return assigned_gb_indices.astype('int')

def fuzzy_similarity(t_data, sigma=0, k=2):
    """
    计算模糊相似性：
      1. 通过递归分裂（splits）方法将数据集划分为多个小球，
      2. 计算每个小球的中心，然后将原始数据点分配到离其最近的小球中心，
      3. 根据两两数据点间中心距离计算得到相似性矩阵 tp，并将小于 sigma 的值置零。
    
    返回：point_to_gb（数据点分组索引）以及 tp（模糊相似性矩阵）。
    """
    t_n, t_m = t_data.shape
    gb_list = [t_data]
    num = np.ceil(t_n ** 0.5)
    while True:
        prev_ball_count = len(gb_list)
        gb_list = splits(gb_list, num=num, k=k)
        if prev_ball_count == len(gb_list):
            break
    gb_center = np.zeros((len(gb_list), t_m))
    for idx, gb in enumerate(gb_list):
        gb_center[idx], _ = calculate_center_and_radius(gb)
    point_to_gb = assign_points_to_closest_gb(t_data, gb_center)
    # 计算每个数据点对应的球心构成的矩阵
    point_center = np.zeros((t_n, t_m))
    for i in range(t_n):
        point_center[i] = gb_center[point_to_gb[i]]
    tp = 1 - cdist(point_center, point_center) / t_m
    tp[tp < sigma] = 0
    return point_to_gb, tp

def compute_membership_matrix(GBi_dict, attr):
    """
    根据 GBi_dict 中指定属性（例如 "a1"）的信息，构造包含矩阵。  
    行数为该属性对应的球数量（按球号排序），  
    列数为所有数据点（假定数据点标记形如 "x1", "x2", ...）。
    若数据点 xi 属于该球，则对应位置记为 1，否则记为 0。
    """
    keys = {key: value for key, value in GBi_dict.items() if key.startswith(f"GB[{attr},")}
    if not keys:
        return np.array([[]])
    sorted_keys = sorted(keys.keys(), key=lambda k: int(k.split(',')[1].rstrip(']')))
    
    all_points = set()
    for key in sorted_keys:
        for point in keys[key]:
            if point.startswith('x'):
                try:
                    all_points.add(int(point[1:]))
                except:
                    pass
    if not all_points:
        return np.array([[]])
    max_point = max(all_points)
    n_points = max_point  # 假设数据点从 x1 到 x_max
    membership_matrix = np.zeros((len(sorted_keys), n_points), dtype=int)
    
    for row, key in enumerate(sorted_keys):
        for point in keys[key]:
            try:
                col_index = int(point[1:]) - 1
                membership_matrix[row, col_index] = 1
            except:
                continue
    return membership_matrix

# ─── 修改 1：打印函数 ──────────────────────────────────────────────
def print_membership_matrices(GBi_dict, attrs=None):
    """
    打印任意数量属性的包含矩阵。
    attrs:
        - None  : 自动从 GBi_dict 提取全部属性并排序
        - list  : 指定如 ["a1","a2",...] 的属性列表
    """
    if attrs is None:
        # 从 key 形如 'GB[a3,2]' 中抓 a3
        attrs = sorted({key.split(',')[0][3:]  # 取 a3
                        for key in GBi_dict})
    for attr in attrs:
        mat = compute_membership_matrix(GBi_dict, attr)
        print(f"\n属性 {attr} 的包含矩阵:")
        if mat.size == 0:
            print("无数据")
        else:
            for row in mat:
                print(" ".join(map(str, row)))

def GBFRD(data, sigma=0):
    """
    …原注释略…
    计算完每个属性的 low_appr 后，累计求和得到依赖度 dep[a{i+1}]
    """
    n, m = data.shape
    attr_indices = np.arange(m)
    
    # 新增：用 dict 保存各属性依赖度
    dependency_dict = {}
    
    for idx1, l1 in enumerate(attr_indices):
        print(f"\n处理属性 a{l1+1}:")
        # ——① 单属性分组——
        point_to_gb_single, _ = fuzzy_similarity(data[:, [l1]], sigma, k=2)
        GBi_number(idx1, point_to_gb_single)          # 保存全局 GBi_dict
        
        # ——② 全属性相似度 -> 下近似矩阵——
        _, sim_mat_all = fuzzy_similarity(data, sigma, k=2)
        rel_mat_P_N = 1 - sim_mat_all                 # 下近似矩阵
        
        # ——③ 当前属性的包含矩阵——
        membership_matrix = compute_membership_matrix(GBi_dict, f"a{l1+1}")
        if membership_matrix.size == 0:
            print(f"属性 a{l1+1} 的包含矩阵为空")
            dependency_dict[f"a{l1+1}"] = 0.0
            continue
        
        # ——④ 依赖度计算——
        dep_val = 0.0                                # 本属性依赖度
        for i in range(membership_matrix.shape[0]):  # 遍历所有球
            low_appr = np.maximum(rel_mat_P_N, membership_matrix[i]).min(axis=1)
            dep_val += low_appr.sum()                # 球内 low_appr 求和累加
            print(f"属性 a{l1+1}，球 {i+1} 的 low_appr: {low_appr}")
        
        dependency_dict[f"a{l1+1}"] = dep_val /n     # 保存结果
        print(f"属性 a{l1+1} 的依赖度 = {dep_val/n}")
    
    return dependency_dict          # 把 dict 返回，主程序可以打印

if __name__ == '__main__':
    # 读取数据并归一化（请确保文件路径正确，数据为数值型）
    #data = pd.read_csv("Code/guiyihua_test.csv").values
    data = pd.read_csv("cleaned_data123.csv").values
    ID = (data >= 1).all(axis=0) & (data.max(axis=0) != data.min(axis=0))
    scaler = MinMaxScaler()
    if any(ID):
        data[:, ID] = scaler.fit_transform(data[:, ID])
    
    sigma = 0.5
    dep = GBFRD(data, sigma)


    
    # print("\n最终存储的 GBi_dict:")
    # for key, value in GBi_dict.items():
    #     print(f"{key} = {value}")

    # m = data.shape[1]                       # 列数＝属性数
    # attrs = [f"a{i+1}" for i in range(m)]   # 动态生成 a1,a2,…,am

    # print_membership_matrices(GBi_dict, attrs)   # 这里用动态 attrs


    print("\n各属性依赖度：")
    for k, v in dep.items():
        print(f"{k}: {v}")